#coding=utf-8
# Copyright 2011-2013 Maxim Petrov <maximpetrov@yahoo.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 3 or any later version.

from local_revision_panel import LocalRevisionPanel
from console_panel import ConsolePanel
from workdir_panel import WorkDirPanel
from incoming_panel import IncomingPanel
from outgoing_panel import OutgoingPanel
from conflicts_panel import ConflictsPanel
from diff_panel import DiffPanel
from view_panel import ViewPanel
from repository import Repository
from message_window import ErrorWindow, KeycodeWindow
from ok_cancel_window import OKCancelWindow
from prompt_window import PromptWindow
from settings import Settings
from color_scheme import ColorScheme
import curses.ascii
import gettext
import os
import threading
import sys
import codecs

class WrongUsingError(Exception):
    """
    Исключение порождается при использовании методов класса без with.
    """

    def __str__(self):
        return 'Using class methods outside with statement'

    def __unicode__(self):
        return u'Using class methods outside with statement'

class MainWindow(object):
    """Главный класс приложения"""


    @property
    def window(self):
        """
        Getter для приватного атрибута __window
        (главного окна получаемого из initscr)
        """

        return self.__window

    def str_to_internal(self, str, is_filename=False):
        """
        Перевод внешних строк во внутреннее представление (юникод)

        Аргументы:
            str - переводимая строка.
            is_filename - является ли строка именем файла, полученным от репозитория.

        Возвращаемое значение:
            Переведенная (в юникод) строка.
        """

        if isinstance(str, unicode):
            return str
        if is_filename:
            if self.__filesystem_encoding != self.__encoding:
                try:
                    self.__filesystem_encoding = self.UTF8
                    return str.decode(self.UTF8)
                except UnicodeDecodeError as e:
                    try:
                        return str.decode(self.__encoding)
                    except UnicodeDecodeError as e:
                        pass
                    finally:
                        self.__filesystem_encoding = self.__encoding
            else:
                try:
                    return str.decode(self.__encoding)
                except UnicodeDecodeError as e:
                    pass
            self.__filesystem_encoding = None
            raise e
        else:
            for enc in (self.UTF8, self.__encoding):
                try:
                    return str.decode(enc)
                except UnicodeDecodeError as e:
                    pass
            raise e

    def str_to_external(self, str, is_filename=False):
        """
        Перевод строк во внешнее представление (системную кодировку)

        Аргументы:
            str - переводимая строка.
            is_filename - является ли строка именем файла, передаваемым в репозиторий.

        Возвращаемое значение:
            Переведенная (в системную кодировку) строка.
        """

        if not isinstance(str, unicode):
            return str
        if is_filename:
            return str.encode(self.__filesystem_encoding or self.UTF8)
        return str.encode(self.__encoding)

    def expandtabs(self, str, tab_size=4):
        """
        Замена табуляций в строке на пробелы

        Аргументы:
            str - входная строка.
            tab_size - сдвиг табуляции.

        Возвращаемое значение:
            (строка[, (положение табуляции в старой строке, начало вставленных пробелов в новой строке, количество вставленных пробелов), ...]).
        """

        result = [""]
        j = 0

        for (i, ch) in enumerate(str):
            if ch == '\t':
                count = (tab_size - j % tab_size)
                result += [(i, j, count)]
                result[0] += ' ' * count
                j = (j / tab_size + 1) * tab_size
            else:
                result[0] += ch
                j += 1

        return result

    def __init__(self, workdir, encoding):
        """
        Конструктор главного окна.

        Аргументы:
            workdir - рабочий каталог.
            encoding - кодировка системы.
        """
        # Последовательность панелей, размещенных в главном окне
        self.__panels = None
        self.__console_x = 0
        self.__console_y = 0
        self.__console_width = 0
        self.__console_height = 0
        # Индекс активной панели
        self.__active_panel = -1
        # Индекс активного слоя
        self.__active_layer = -1
        # Главное окно
        self.__window = None
        # Текущая ширина терминала
        self.__width = 0
        # Текущая высота терминала
        self.__height = 0
        # Горячие клавиши главного окна.
        # {номер F-клавиши: подпись, ...}
        self.__hotkeys = {2: _(u'Pull'), 3: _(u'Push'), 10: _(u'Exit')}
        # Значение, передаваемое в curses.curs_set для видимого курсора
        self.visible_cursor = None
        # Инициализаванный экземпляр класса Repository
        self.repo_util = None
        # Обрабатываем ли нажатия клавиш?
        self.__handle_keypress = True
        # Блокировка для __handle_keypress
        self.__handle_keypress_lock = threading.RLock()
        # Рабочий каталог
        self.__workdir = os.path.abspath(workdir)
        self.__workdir_before_start = os.getcwd()
        os.chdir(self.__workdir)
        # Настройки приложения
        self.__no_resize = False
        # Список запускаемых объектов, которые предстоит запустить
        self.__to_invoke = []
        self.__needed_keycodes = {u'KEY_CONTROL_UP': u'Ctrl+Up', u'KEY_CONTROL_DOWN': u'Ctrl+Down',
                                  u'KEY_SHIFT_LEFT': u'Shift+Left', u'KEY_SHIFT_RIGHT': u'Shift+Right',
                                  u'KEY_SHIFT_F12': u'Shift+F12', u'KEY_CONTROL_F12': u'Ctrl+F12'}
        self.__encoding = codecs.lookup(encoding).name
        self.__filesystem_encoding = None
        self.UTF8 = codecs.lookup('utf8').name
        self.settings = Settings(self.__workdir, self)
        self.__workdir = self.str_to_internal(self.__workdir)

    def __enter__(self):
        """
        Инициализует окно для работы.
        Вызывается при использовании конструкции with,
        нельзя использовать экземпляр класса без этой конструкции.
        """
        self.__window = curses.initscr()
        self.color_scheme = ColorScheme()
        curses.noecho()
        curses.cbreak()
        self.__window.keypad(1)
        self.visible_cursor = curses.curs_set(0)
        self.__window.timeout(10)
        # Получение размеров терминала
        self.__height, self.__width = self.__window.getmaxyx()
        curses.def_prog_mode()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
        Деструктор. Вызывается при завершении работы c экземпляром
        класса через with.  Восстанавливает состояние терминала.
        """

        #ставим курсор на последнюю строку на последнюю позицию
        #так при выходе не остаётся следов
        self.__window.move(self.__height - 1,self.__width - 1)
        if self.visible_cursor is not None:
            curses.curs_set(self.visible_cursor)
        if self.__window is not None:
            self.__window.keypad(0)
        curses.nocbreak()
        curses.echo()
        curses.endwin()
        if self.repo_util is not None and self.repo_util.clear is not None:
            self.repo_util.clear()
        os.chdir(self.__workdir_before_start)

    def __print_hint_line(self):
        """
        Функция отрисовывает подсказки горячих клавиш в нижней строке.
        Делит всю строку на 10 частей и выводит используемые горячие клавиши
        в соответствующих местах
        """

        # Сначала строка очищается от старых символов
        self.__window.move(self.__height - 1, 0)
        self.__window.clrtoeol()
        # Собираются вместе общие подсказки и подсказки активной панели
        hints = self.__hotkeys.copy()
        hints.update(self.__panels[self.__active_panel].hotkeys)
        # Вычисляется количество символов на одну подсказку
        hint_len = self.__width / 10
        # Собирается строка из подсказок
        key_str = u''
        for i in range(1, 11):
            if i in hints:
                s = u'{0:2}{1}'.format(i, hints[i])
            else:
                s = u'{0:2}'.format(i)
            key_str += s[:hint_len].ljust(hint_len)
        # Если строка не влазит, то она обрезается
        key_str = key_str[:self.__width - 1]
        self.__window.addstr(self.__height - 1, 0, self.str_to_external(key_str), curses.A_REVERSE)
        # Выделяем цифры
        for i in range(0, 10):
            self.__window.chgat(self.__height - 1, i * hint_len, 2,
                                curses.A_NORMAL)

    def __resize(self, window=None, content_only=False):
        """
        Функция, обрабатывающая изменение размера терминала.  Вносит
        изменения в размеры отображаемых элементов на основании новых
        размеров терминала.
        """

        self.__window.clear()
        # Получаем размеры терминала и переразмещаем панели
        self.__height, self.__width = self.__window.getmaxyx()

        # Проверяем на минимальные размеры окна, чтобы не падать
        if self.__width < 4 or self.__height < 2:
            return

        tmp_height = 3
        if self.__height > 2:
            tmp_height = self.__height

        #if self.__active_layer == 2:
        self.__console_x = 0
        self.__console_y = 0
        self.__console_width = self.__width
        self.__console_height = tmp_height - 1
        
        wdw = self.__window.subwin(tmp_height - 1, self.__width / 2, 0, 0)
        revw = self.__window.subwin(tmp_height - 1,
                                    self.__width - self.__width / 2,
                                    0, self.__width / 2)
        conw = self.__window.subwin(self.__console_height, self.__console_width,
                                    self.__console_y, self.__console_x)
        inw = self.__window.subwin(tmp_height / 2, self.__width / 2, 0, 0)
        outw = self.__window.subwin(tmp_height / 2, self.__width / 2, 0,
                                    self.__width - self.__width / 2)
        cpw = self.__window.subwin(tmp_height - tmp_height / 2 - 1,
                                   self.__width, tmp_height / 2, 0)
        dpw = self.__window.subwin(tmp_height - 1, self.__width, 0, 0)
        vpw = self.__window.subwin(tmp_height - 1, self.__width, 0, 0)

        # Перерисовываем строку подсказок
        if self.__active_panel >= 0 and self.__panels[self.__active_panel]:
            self.__print_hint_line()
        self.__window.refresh()
        self.__panels[0].redraw(wdw)
        self.__panels[1].redraw(revw)
        self.__redrawing = True
        self.__panels[2].redraw(inw, content_only)
        self.__panels[3].redraw(outw, content_only)
        self.__panels[4].redraw(conw)
        self.__panels[5].redraw(cpw, content_only)
        self.__panels[6].redraw(dpw)
        self.__panels[7].redraw(vpw)
        self.__window.untouchwin()
        if self.__active_panel >= 0 and self.__panels[self.__active_panel]:
            self.__panels[self.__active_panel].set_active()
        # Переразмещаем модальное окно
        if window is not None:
            window.resize(self.__window.subwin(self.__height - 1,
                                               self.__width, 0, 0))
            self.__window.refresh()

    def __change_active_panel(self):
        """Функция делает активной другую панель"""

        new_active_panel = self.__active_panel
        while True:
            if new_active_panel == len(self.__panels) - 1:
                new_active_panel = 0
            else:
                new_active_panel += 1
            if self.__panels[new_active_panel] is not None and \
              self.__panels[new_active_panel].visible:
                break
        if self.__active_panel >= 0 and self.__panels[self.__active_panel]:
            self.__panels[self.__active_panel].set_inactive()
        self.__active_panel = new_active_panel
        # Обновляем подсказку, чтобы вывести горячие клавиши
        # для панели, которая стала активной
        self.__print_hint_line()
        self.__panels[self.__active_panel].set_active()
        # Помещаем курсор на консоль
        #self.__console_panel.set_active()

    def __set_active_layer(self, layer):
        """
        Функция устанавливает видимость панелей для заданного слоя.

        Аргументы:
            layer - номер слоя, в соответствии с которым нужно
                    настроить видимость.
        """

        if self.__active_layer == layer:
            return
        if layer == 0:
            self.__panels[0].visible = True
            self.__panels[1].visible = True
            self.__panels[2].visible = False
            self.__panels[3].visible = False
            self.__panels[4].visible = False
            self.__panels[5].visible = False
            self.__panels[6].visible = False
            self.__panels[7].visible = False
            self.__hotkeys.update({9: _(u'In/Out')})
        elif layer == 1:
            self.__panels[0].visible = False
            self.__panels[1].visible = False
            self.__panels[2].visible = True
            self.__panels[3].visible = True
            self.__panels[4].visible = False
            self.__panels[5].visible = True
            self.__panels[6].visible = False
            self.__panels[7].visible = False
            self.__hotkeys.update({9: _(u'Main')})
        # Слой только с консолью
        elif layer == 2:
            self.__panels[0].visible = False
            self.__panels[1].visible = False
            self.__panels[2].visible = False
            self.__panels[3].visible = False
            self.__panels[4].visible = True
            self.__panels[5].visible = False
            self.__panels[6].visible = False
            self.__panels[7].visible = False
            #self.__console_panel = None
            del self.__hotkeys[9]
        # Слой с diff панелью
        elif layer == 3:
            self.__panels[0].visible = False
            self.__panels[1].visible = False
            self.__panels[2].visible = False
            self.__panels[3].visible = False
            self.__panels[4].visible = False
            self.__panels[5].visible = False
            self.__panels[6].visible = True
            self.__panels[7].visible = False
            self.__hotkeys.update({9: _(u'Main')})
        # Слой с view панелью
        elif layer == 4:
            self.__panels[0].visible = False
            self.__panels[1].visible = False
            self.__panels[2].visible = False
            self.__panels[3].visible = False
            self.__panels[4].visible = False
            self.__panels[5].visible = False
            self.__panels[6].visible = False
            self.__panels[7].visible = True
            self.__hotkeys.update({9: _(u'Main')})
        else:
            return
        self.__active_layer = layer
        # Поскольку при переключении в первый слой возможно появление модальных
        # окон по ходу обновления (тех, что для запроса различных данных),
        # приходиться делать 2 обновления: первое, чтобы обновить содержимое
        # панелей и запросить все данные.
        if layer == 1:
            self.__no_resize = True
            self.__resize(content_only=True)
            self.__no_resize = False
        self.__resize()
        self.__change_active_panel()


    def show_modal_window(self, window):
        """Функция вызывает переданное ей модальное окно"""

        window.visible_cursor = self.visible_cursor
        curses.curs_set(0)
        self.__window.untouchwin()
        window.show(self.__window.subwin(self.__height - 1,
                    self.__width, 0, 0), self)
        self.__handle_keypress_lock.acquire()
        while True:
            ch = self.__window.getch()
            if ch < 256 and ch > 127:
                if self.__encoding == self.UTF8:
                    ch = self.__get_unicode_char(ch)
                else:
                    ch = ord(chr(ch).decode(self.__encoding))
                uni = True
            else:
                uni = False
            # Обработка изменения размера терминала
            if not uni and ch == curses.KEY_RESIZE:
                self.__resize(window, True)
            # Остальные нажатия пересылаются в модальное окно
            else:
                # Если нажат Backspace, который ^H или ASCII DEL,
                # то считаем его обычным KEY_BACKSPACE
                if not uni and (ch == curses.ascii.BS or \
                                ch == curses.ascii.DEL):
                    ch = curses.KEY_BACKSPACE;
                result = window.handle_keypress(ch, uni)
                if result is not None:
                    # Перерисовываем главное окно, чтобы стереть
                    # модальное окно
                    if not self.__no_resize:
                        self.__resize()
                    curses.curs_set(0)
                    self.__handle_keypress_lock.release()
                    #self.__console_panel.set_active()
                    return result


    def show_diff_panel(self, content=None, title=None):
        """
        Функция переключает вывод diff панели

        Аргументы:
            content - содержимое diff панели, которое передается в её set_content
                      или None, когда панель надо убрать
            title - заголовок (пара) для панелей
        """

        if self.__active_layer == 3:
            self.__active_panel = self.__active_panel_before_diff_or_view - 1 or 0
            self.__set_active_layer(self.__active_layer_before_diff_or_view)
        else:
            self.__active_layer_before_diff_or_view = self.__active_layer
            self.__panels[6].set_content(content[0], content[1], title)
            self.__active_panel_before_diff_or_view = self.__active_panel
            self.__set_active_layer(3)

    def show_view_panel(self, content=None, title=None):
        """
        Функция переключает вывод view панели

        Аргументы:
            content - содержимое view панели, которое передается в её set_content
                      или None, когда панель надо убрать
            title - заголовок для панели
        """

        if self.__active_layer == 4:
            self.__active_panel = self.__active_panel_before_diff_or_view - 1 or 0
            self.__set_active_layer(self.__active_layer_before_diff_or_view)
        else:
            self.__active_layer_before_diff_or_view = self.__active_layer
            self.__panels[7].set_content(content, title)
            self.__active_panel_before_diff_or_view = self.__active_panel
            self.__set_active_layer(4)

    def show_window(self):
        """
        Основная функция класса.  Вызов приводит к отображению главного
        окна.
        """

        if self.__window is None:
            raise WrongUsingError()

        # Создание панелей
        wdp = WorkDirPanel(self)
        lrp = LocalRevisionPanel(self)
        self.__console_panel = ConsolePanel(self, self.__window, self.__resize, self.__workdir)
        self.__console_panel.workdir_panel = wdp
        inp = IncomingPanel(self)
        outp = OutgoingPanel(self)
        cp = ConflictsPanel(self)
        dp = DiffPanel(self)
        vp = ViewPanel(self)
        self.repo_util = Repository(self.__workdir, self, console = self.__console_panel)
        wdp.set_workdir(self.str_to_internal(self.repo_util.path))
        wdp.revision_panel = lrp
        lrp.workdir_panel = wdp
        inp.conflicts_panel = cp
        outp.conflicts_panel = cp
        self.__panels = [wdp, lrp, inp, outp, self.__console_panel, cp, dp, vp]
        self.__set_active_layer(0)
        state_before_ctrl_o = None
        self.check_keycodes()
        while True:
            if self.__handle_keypress_lock.acquire():
                self.__invoke_queued()
                ch = self.__window.getch()
                if ch == -1:
                    continue
                if ch < 256 and ch > 127:
                    if self.__encoding == self.UTF8:
                        ch = self.__get_unicode_char(ch)
                    else:
                        ch = ord(chr(ch).decode(self.__encoding))
                    uni = True
                else:
                    uni = False
                # Если нажат Backspace, который ^H или ASCII DEL,
                # то считаем его обычным KEY_BACKSPACE
                if not uni and (ch == curses.ascii.BS or \
                                ch == curses.ascii.DEL):
                    ch = curses.KEY_BACKSPACE;
                #self.__handle_keypress_lock.release()
                if not uni and ch == -1:
                    self.__handle_keypress_lock.release()
                    continue
                # Выход из приложения по клавише F10
                elif not uni and ch == curses.KEY_F10:
                    self.__handle_keypress_lock.release()
                    break
                # Обработка изменения размера терминала
                elif not uni and  ch == curses.KEY_RESIZE:
                    self.__resize()
                # Скачивание обновлений
                elif not uni and ch == curses.KEY_F2:
                    if not self.repo_util.pull():
                        msg = ErrorWindow(self.repo_util.server_error)
                        self.show_modal_window(msg)
                        self.repo_util.server_error = None
                    else:
                        self._update_inout_panels(False)
                # Проталкивание обновлений
                elif not uni and ch == curses.KEY_F3:
                    if not self.repo_util.push():
                        msg = ErrorWindow(self.repo_util.server_error)
                        self.show_modal_window(msg)
                        self.repo_util.server_error = None
                    else:
                        self._update_inout_panels(False)
                # Переход к In/Out и обратно по клавише F9
                elif not uni and ch == curses.KEY_F9:
                    if self.__active_layer == 0:
                        self.__set_active_layer(1)
                        # Вывод сообщения об ошибке сервера при первом отображении
                        if self.repo_util.server_error is not None:
                            msg = ErrorWindow(self.repo_util.server_error)
                            self.show_modal_window(msg)
                            # Сброс ошибки после отображения
                            self.repo_util.server_error = None
                    else:
                        self.__set_active_layer(0)
                # По нажатию табуляции переход в следующую панель
                elif ch == ord('\t'):
                    self.__change_active_panel()
                elif ch == curses.ascii.ctrl(ord('O')): #Ctrl-O
                    if state_before_ctrl_o is None:
                        state_before_ctrl_o = (self.__active_layer,
                          self.__active_panel)
                        self.__set_active_layer(2)
                    else:
                        (active_layer,
                          active_panel) = state_before_ctrl_o
                        self.__set_active_layer(active_layer)
                        self.__active_panel = active_panel - 1
                        self.__change_active_panel()
                        state_before_ctrl_o = None
                elif ch == curses.ascii.ctrl(ord('B')): # Ctrl-B - Rollback (временно)
                    self.repo_util.rollback(self)
                elif ch == curses.ascii.ctrl(ord('K')): # Ctrl+K - переназначение нестандартных клавиш (временно)
                    self.check_keycodes(True)
                # Клавиши, которые всегда передаются в ConsolePanel
                #elif (uni or \
                #    (ch <= 127 and curses.ascii.isprint(ch))) and \
                #    self.__console_panel is not None:
                #        self.__console_panel.handle_keypress(ch, uni)
                #elif not uni and  (ch == curses.KEY_BACKSPACE or \
                #  ch == curses.KEY_DC or ch == curses.KEY_LEFT or \
                #  ch == curses.KEY_RIGHT or ch == ord('\n')) and \
                #  self.__console_panel is not None:
                #    if self.__console_panel.is_input_buffer_empty():
                #        self.__panels[self.__active_panel].\
                #          handle_keypress(ch, uni)
                #    else:
                #        self.__console_panel.handle_keypress(ch, uni)
                # Остальные нажатия пересылаются в активную панель
                else:
                    self.__panels[self.__active_panel].handle_keypress(ch, uni)
                self.__handle_keypress_lock.release()
                #self.__console_panel.set_active()


    def _update_inout_panels(self, check=True):
        """
        Функция обновляет содержимое панелей In, Out и Conflicts

        Аргументы:
            check - если True, то заново скачиваются обновления с сервера,
                    если False, то нет (полезно, если сделаны изменения с
                    локальным репозиторием)
        """

        self.__no_resize = True
        if check:
            self.repo_util.update_bundle_repo()
        self.__panels[2]._update_content()
        self.__panels[3]._update_content()
        self.__panels[5].update()
        if self.repo_util.server_error is not None:
            msg = ErrorWindow(self.repo_util.server_error)
            self.show_modal_window(msg)
            self.repo_util.server_error = None
        else:
            self.__window.untouchwin()
        self.__no_resize = False
        self.__resize()

    def __get_unicode_char(self, first_byte):
        """
        Функция досчитывает байт юникода

        Аргументы:
            first_byte - считанный первый байт.

        Возвращаемое значение:
            Код считанного символа.
        """

        # Вычисляем количество дополнительных байт.  bin выдает двоичное число
        # в формате 0bxxxxxxxx.  Поскольку нужно получить число единиц кроме
        # первой, берем полученную строку с 3 символа и ищем первый 0.  Затем
        # обрезаем начальные единицы.
        i = bin(first_byte)[3:].find(u'0')
        result = int(bin(first_byte)[i + 3:], 2)
        # Считываем вычисленное выше число дополнительных байт и припысываем
        # 6 младших бит к текущему результату.
        while i > 0:
            result <<= 6
            result |= (self.__window.getch() & 0x3F)
            i -= 1
        return result


    def hide(self):
        """
        Прячет MainWindow. Возвращает терминал к нормальному состоянию
        (как до запуска curses), запрещает перехват нажатий клавиш
        """
        self.__handle_keypress_lock.acquire()
        #self.__handle_keypress = False
        #self.__handle_keypress_lock.release()
        #curses.flushinp()
        #self.__window.move(self.__height - 1,self.__width - 1)
        #if self.visible_cursor is not None:
        #    curses.curs_set(self.visible_cursor)
        #if self.__window is not None:
        #    self.__window.keypad(0)
        #curses.nocbreak()
        #curses.echo()

        curses.reset_shell_mode()
        print



    def unhide(self):
        """
        Возвращает MainWindow. Возвращает терминал к состоянию, пригодному
        для работы окна программы (как после вызова __enter__), разрешает перехват
        нажатий клавиш
        """
        sys.stdout.flush()
        sys.stderr.flush()
        curses.reset_prog_mode()
        self.__resize()
        curses.flushinp()
        #self.__window = curses.initscr()
        #curses.noecho()
        #curses.cbreak()
        #self.__window.keypad(1)
        #curses.flushinp()
        #self.visible_cursor = curses.curs_set(0)
        ## Получение размеров терминала
        #self.__height, self.__width = self.__window.getmaxyx()
        #self.__resize()
        ##self.__handle_keypress_lock.acquire()
        ##self.__handle_keypress = True
        self.__handle_keypress_lock.release()

    def invoke_later(self, runnable):
        """
        Откладывает для запуска код в этом потоке по "просьбе" другого потока.
        Вызывается из потока, который хочет выполнить код в этом потоке.
        Поскольку curses не являются
        потокобезопасными, весь код связанный с отрисовкой GUI следует выполнять
        в одном потоке.
        Написано по мотивам SwingUtilities.invokeLater() из Java.

        Аргументы:
            runnable - объект с исполняемым кодом. Код должен выполняться
                при вызове метода run()
        """
        self.__to_invoke.append(runnable)

    def __invoke_queued(self):
        """
        Запускает весь код, отложенный для запуска другими потоками.
        """
        invoked_flag = False
        for i in self.__to_invoke:
            invoke_flag = True
            i.run()
        self.__to_invoke = []
        if invoked_flag:
            self.__resize()

    def check_keycodes(self, force=False):
        """
        Проверяет, заданы ли в настройках коды для нестандартных клавиш (Ctrl+Up, Ctrl+Down и т.д.)

        Аргументы:
            force - если True, то запрашивать нажатия даже тех клавиш, которые уже назначены
        """

        for key, name in self.__needed_keycodes.iteritems():
            if force or self.settings.get(u'Keycodes', key, False) is None:
                msg = KeycodeWindow(_(u'Please press \'{0}\'').format(name))
                result = self.show_modal_window(msg)
                if result[u'button'] == u'Ok':
                    self.settings.set(u'Keycodes', key, result[u'code'])
